#!/usr/bin/ruby
# -*- ruby -*-
#
# http://www.reddit.com/r/dailyprogrammer/comments/11par4/10182012_challenge_104_intermediate_bracket_racket/

BRACES = [%w{( )}, %w{< >}, %w{{ }}, %w{[ ]}].inject({}) { |r, p| r.merge(p.first => p.last) }
R_BRACES = BRACES.inject({}) { |r, (k, v)| r.merge(v => k) }

def opening?(ch)
    return BRACES.keys.include?(ch)
end

def closing?(ch)
    return BRACES.values.include?(ch)
end

class BraceStack
    attr_reader :braces

    def initialize
        @braces = []
    end

    def push(ch)
        @braces << ch if opening?(ch)
    end

    def pop(ch)
        if closing?(ch)
            raise "No opening brace '#{R_BRACES[ch]}' for '#{ch}'" if @braces.empty?

            raise "Expected '#{BRACES[@braces.last]}' to close '#{@braces.last}', not '#{ch}'" \
                unless BRACES[@braces.last] == ch

            # Only pop if it's the character is the matching brace
            # for the last popped brace
            @braces.pop
        end
    end
end

ARGV.each do |str|
    balanced = false
    msg = nil

    b = BraceStack.new

    begin
        str.split(//).each do |ch|
            b.push(ch)
            b.pop(ch)
        end

        if b.braces.empty?
            balanced = true
        else
            msg = "Expecting closing braces #{b.braces.map { |c| "'" + BRACES[c] + "'" }.reverse.join(", ")}"
        end
    rescue Exception => e
        msg = e.message
    end

    puts "#{str} => #{balanced ? 'balanced' : 'unbalanced'}" + (msg ? " : #{msg}" : "")
end
